﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Windows.Forms;
using Common.Data;
using Common.Entities;
using Common.Enums;
using Common.Extensions;
using Common.Packets;
using Lidgren.Network;
using SamplesCommon;

namespace ChatServer
{
    public partial class Main : Form
    {
        private Process proc;
        private PerformanceCounter cpuCounter;
        private long maxMem = 0;
        private long minMem = long.MaxValue;
        private float maxCPU = 0;
        private float minCPU = float.MaxValue;
        public static NetServer Server;
        private static Stopwatch StopWatch = new Stopwatch();
        private bool _isExiting = false;
        public NetClient MasterServerClient;
        NetPeerConfiguration config = new NetPeerConfiguration("ShootLife");
        NetPeerConfiguration masterServerConfig = new NetPeerConfiguration("Master");

        public Dictionary<string, string> PlayersTokens = new Dictionary<string, string>();
        public List<PlayerChat> Players = new List<PlayerChat>();
        public Main()
        {
            InitializeComponent();
            StopWatch.Start();
            proc = Process.GetCurrentProcess();
            cpuCounter = new PerformanceCounter("Process", "% Processor Time", proc.ProcessName, true);
            config.Port = 14244;
            //config.SimulatedLoss = 0.02f;
            config.MaximumConnections = 25600;
            config.SetMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval, true);
            Server = new NetServer(config);
            Server.RegisterReceivedCallback(new SendOrPostCallback(ServerLoop));
            Server.Start();
            ConnectToMaster();
        }
        public void AddConsoleLog(string text, ConsoleMessageType messageType)
        {
            if (!_isExiting)
            {
                string log = string.Format("{0} {1} {2}", DateTime.Now,
                                           messageType.GetDescription(), text);
                try
                {

                    this.Invoke(
                        new Action(
                            () =>
                            {
                                NativeMethods.AppendText(logTb,
                                                         log);
                            }));

                }
                catch
                { }
                using (FileStream fs = new FileStream("Log.log", FileMode.Append, FileAccess.Write))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        sw.WriteLine(log);
                        sw.Close();
                    }
                    fs.Close();
                }

            }
        }

        public void DisconnectFromMaster(string reason)
        {
            if (MasterServerClient != null)
            {
                if (MasterServerClient.ConnectionStatus != NetConnectionStatus.Disconnected)
                {
                    MasterServerClient.Shutdown(reason);
                }
            }
        }
        public void ConnectToMaster()
        {
            DisconnectFromMaster("Reconnect...");
            MasterServerClient = new NetClient(masterServerConfig);
            MasterServerClient.Start();
            MasterServerClient.RegisterReceivedCallback(MasterServerMessage);
            NetOutgoingMessage mes = MasterServerClient.CreateMessage();
            mes.Write("SomePassword");
            mes.Write((int)ServerType.ChatServer);
            mes.Write(14244);
            MasterServerClient.Connect("localhost", 14243, mes);
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }

        public void MasterServerMessage(object peer)
        {
            NetIncomingMessage im;
            while ((im = MasterServerClient.ReadMessage()) != null)
            {
                switch (im.MessageType)
                {
                    case NetIncomingMessageType.DebugMessage:
                    case NetIncomingMessageType.ErrorMessage:
                    case NetIncomingMessageType.WarningMessage:
                    case NetIncomingMessageType.VerboseDebugMessage:
                        string text = im.ReadString();
                        break;
                    case NetIncomingMessageType.StatusChanged:
                        NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();
                        if (status == NetConnectionStatus.Disconnected)
                        {
                            AddConsoleLog("Connection to master lost:" + im.ReadString() + ". Trying to reconnect...", ConsoleMessageType.Warning);
                            ConnectToMaster();//потеряли соединение, пробуем восстановить
                        }
                        else if (status == NetConnectionStatus.Connected)
                        {
                            AddConsoleLog("Successfully connected to Master!", ConsoleMessageType.Info);
                        }
                        break;
                    case NetIncomingMessageType.Data:
                        ServerPacketType packet = (ServerPacketType)im.ReadInt16();
                        ProcessServerPacket(packet, im);
                        break;
                    default:
                        //неизвестный тип данных
                        break;
                }
            }
        }

        public void ProcessServerPacket(ServerPacketType serverPacketType, NetIncomingMessage msg)
        {
            string login;
            switch (serverPacketType)
            {
                case ServerPacketType.Authorize:

                    break;
                case ServerPacketType.ChangePlayerLocation://смена "комнаты" нахождения персонажа (карта или глобал)
                    login = msg.ReadString();
                    int location = msg.ReadInt32();
                    PlayerChat player = Players.FirstOrDefault(p => p.Name == login);
                    if (player != null)
                        player.Location = location;
                    break;
                case ServerPacketType.GameServersList:
                    //на чат сервер такое никогда не должно прийти
                    break;
                case ServerPacketType.PlayerExit://игрок вышел из игры, удаляем всю инфу о нем
                    login = msg.ReadString();
                    bool kick = msg.ReadBoolean();
                    if (PlayersTokens.ContainsKey(login))
                    {
                        PlayersTokens.Remove(login);
                    }
                    if (kick)
                    {
                        Players.RemoveAll(p => p.Name == login);
                        NetConnection connection = Server.Connections.FirstOrDefault(p => p.Tag as string == login);
                        if (connection != null)
                        {
                            connection.Disconnect("Kicked by master");
                        }
                    }
                    break;
                case ServerPacketType.AuthToken://токен авторизации от мастер сервера, для авторизации игрока по временному токену, а не по логин-паролю
                    login = msg.ReadString();
                    string token = msg.ReadString();
                    if (PlayersTokens.ContainsKey(login))
                    {
                        PlayersTokens[login] = token;
                    }
                    else
                    {
                        PlayersTokens.Add(login, token);
                    }
                    break;
                default:
                    break;
            }
        }

        public void ServerLoop(object peer)
        {
            NetIncomingMessage msg;
            while ((msg = Server.ReadMessage()) != null)
            {
                switch (msg.MessageType)
                {
                    case NetIncomingMessageType.VerboseDebugMessage:
                    case NetIncomingMessageType.DebugMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Info);
                        break;
                    case NetIncomingMessageType.WarningMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Warning);
                        break;
                    case NetIncomingMessageType.ErrorMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Error);
                        break;
                    case NetIncomingMessageType.StatusChanged:
                        NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte();
                        if (status == NetConnectionStatus.Connected)
                        {

                        }

                        break;
                    case NetIncomingMessageType.ConnectionApproval:
                        try
                        {
                            string login = msg.ReadString();
                            string token = msg.ReadString();
                            if (PlayersTokens.ContainsKey(login))
                            {
                                if (PlayersTokens[login] == token)
                                {
                                    msg.SenderConnection.Approve();
                                    msg.SenderConnection.Tag = login;
                                }
                                else
                                {
                                    msg.SenderConnection.Deny("Invalid token");
                                }
                            }
                            else
                            {
                                msg.SenderConnection.Deny("No info about that player");
                            }

                        }
                        catch (NetException)
                        {
                            msg.SenderConnection.Deny("Bad approve data, go away!");
                        }
                        break;
                    case NetIncomingMessageType.Data:

                        break;
                }
            }
        }
        public List<long> MemoryValues = new List<long>();
        public List<float> CPUValues = new List<float>();

        private void countersTimer_Tick(object sender, EventArgs e)
        {
            float cpuValue = cpuCounter.NextValue() / 8.0f;
            CPUValues.Add(cpuValue);
            proc = Process.GetCurrentProcess();

            if (minMem > proc.PrivateMemorySize64)
            {
                minMem = proc.PrivateMemorySize64;
            }
            if (maxMem < proc.PrivateMemorySize64)
            {
                maxMem = proc.PrivateMemorySize64;
            }
            if (minCPU > cpuValue)
            {
                minCPU = cpuValue;
            }
            if (maxCPU < cpuValue)
            {
                maxCPU = cpuValue;
            }
            MemoryValues.Add(proc.PrivateMemorySize64);
            if (MemoryValues.Count > 300)
            {
                MemoryValues.RemoveAt(0);
            }
            if (CPUValues.Count > 300)
            {
                CPUValues.RemoveAt(0);
            }
            cpuMaxLbl.Text = "CPU максимум: " + maxCPU.ToString("N0") + "%";
            cpuMinLbl.Text = "CPU минимум: " + minCPU.ToString("N0") + "%";
            cpuAvLbl.Text = "CPU в среднем за 5 минут: " + CPUValues.Average().ToString("N0") + "%";
            memMaxLbl.Text = "Максимум памяти: " + GetMemoryValue(maxMem);
            memMinLbl.Text = "Минимум памяти: " + GetMemoryValue(minMem);
            memAvLbl.Text = "Памяти в среднем за 5 минут: " + GetMemoryValue((long)MemoryValues.Average());
            cpuLbl.Text = "CPU: " + cpuValue.ToString("N0") + "%";
            memLbl.Text = "Памяти занято: " + GetMemoryValue(proc.PrivateMemorySize64);
            incTrafLbl.Text = "Входящий траффик: " + GetMemoryValue(Server.Statistics.ReceivedBytes);
            outTrafLbl.Text = "Исходящий траффик: " + GetMemoryValue(Server.Statistics.SentBytes);
            totalTrafLbl.Text = "Всего: " + GetMemoryValue(Server.Statistics.SentBytes + Server.Statistics.ReceivedBytes);
            inMessagesLbl.Text = "Входящих сообщений: " + Server.Statistics.ReceivedMessages;
            outMessagesLbl.Text = "Исходящих сообщений: " + Server.Statistics.SentMessages;
            totalMessagesLbl.Text = "Всего: " + (Server.Statistics.ReceivedMessages + Server.Statistics.SentMessages);
            inPacketsLbl.Text = "Входящих пакетов: " + Server.Statistics.ReceivedPackets;
            outPacketsLbl.Text = "Исходящих пакетов: " + Server.Statistics.SentPackets;
            totalPacketsLbl.Text = "Всего: " + (Server.Statistics.ReceivedPackets + Server.Statistics.SentPackets);
            connectionsLbl.Text = "Соединений: " + Server.ConnectionsCount;

            TimeSpan TimeDiff = StopWatch.Elapsed;

            uptimeLbl.Text = string.Format("Время работы: {0:00}:{1:00}:{2:00}:{3:00}",
               TimeDiff.Days,
               TimeDiff.Hours,
               TimeDiff.Minutes,
               TimeDiff.Seconds);
            graphicPanel.Invalidate();
        }

        private void graphicPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            float rectWidth = (float)graphicPanel.Width / 300.0f;
            float maxValueCPU = CPUValues.Max();
            long maxValueMem = MemoryValues.Max();
            for (int i = 0; i < MemoryValues.Count; i++)
            {
                g.FillRectangle(new SolidBrush(Color.Black), graphicPanel.Width - (float)(MemoryValues.Count - i) * rectWidth, graphicPanel.Height - ((float)MemoryValues[i] / (float)maxValueMem * (float)graphicPanel.Height), rectWidth, graphicPanel.Height);
                g.FillRectangle(new SolidBrush(Color.Red), graphicPanel.Width - (float)(MemoryValues.Count - i) * rectWidth, graphicPanel.Height - ((float)CPUValues[i] / (float)maxValueCPU * (float)graphicPanel.Height), rectWidth, graphicPanel.Height);
            }
            g.FillRectangle(new SolidBrush(Color.LightGray), graphicPanel.Width - 30, graphicPanel.Height - 15, 20, 15);
            g.FillRectangle(new SolidBrush(Color.LightGray), graphicPanel.Width - 106, 0, 94, 15);
            g.DrawString(maxValueCPU.ToString("N0") + "% / " + GetMemoryValue(maxValueMem), this.Font, new SolidBrush(Color.Black), graphicPanel.Width - 100, 0);
            g.DrawString("0", this.Font, new SolidBrush(Color.Black), graphicPanel.Width - 25, graphicPanel.Height - 15);
            g.Save();
        }
        public string GetMemoryValue(long value)
        {
            double temp;
            if (value < 1024)
            {
                return value.ToString();
            }
            if (value < 1024 * 1024)
            {
                temp = value / 1024.0d;
                return temp.ToString("N2") + "KB";
            }
            if (value < 1024 * 1024 * 1024)
            {
                temp = value / 1024.0d / 1024.0d;
                return temp.ToString("N2") + "MB";
            }
            else
            {
                temp = value / 1024.0d / 1024.0d / 1024.0d;
                return temp.ToString("N2") + "GB";
            }
        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            AddConsoleLog("Server shutdown by user.", ConsoleMessageType.Info);
            _isExiting = true;
            DisconnectFromMaster("Chat server shutdown");
        }
    }
}
